home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1996 May: Tool Chest / Developer CD Series May 1996 (Tool Chest) (Apple Computer) (1996).iso / Tool Chest / Development Tools & Languages / Dylan Related / Mindy / Mindy 1.2 - portable sources / libraries / dylan / table.dylan < prev    next >
Encoding:
Text File  |  1995-03-15  |  37.9 KB  |  1,073 lines  |  [TEXT/ttxt]

  1. module:        Hash-Tables
  2. Author:        Nick Kramer (nkramer@cs.cmu.edu)
  3. rcs-header: $Header: table.dylan,v 1.12 94/11/22 16:55:22 nkramer Exp $
  4. Synopsis:   Implements <table>, <object-table>, <equal-table>, 
  5.             and <value-table>.
  6.  
  7. //======================================================================
  8. //
  9. // Copyright (c) 1994  Carnegie Mellon University
  10. // All rights reserved.
  11. // 
  12. // Use and copying of this software and preparation of derivative
  13. // works based on this software are permitted, including commercial
  14. // use, provided that the following conditions are observed:
  15. // 
  16. // 1. This copyright notice must be retained in full on any copies
  17. //    and on appropriate parts of any derivative works.
  18. // 2. Documentation (paper or online) accompanying any system that
  19. //    incorporates this software, or any part of it, must acknowledge
  20. //    the contribution of the Gwydion Project at Carnegie Mellon
  21. //    University.
  22. // 
  23. // This software is made available "as is".  Neither the authors nor
  24. // Carnegie Mellon University make any warranty about the software,
  25. // its performance, or its conformity to any specification.
  26. // 
  27. // Bug reports, questions, comments, and suggestions should be sent by
  28. // E-mail to the Internet address "gwydion-bugs@cs.cmu.edu".
  29. //
  30. //======================================================================
  31.  
  32. // This code is a more or less implementation independent.  Almost all
  33. // of the code that is implementation dependent is in the beginning of
  34. // the file; the rest can be found with a search for "mindy".
  35. //
  36. // Author's note: "ht" is my abbreviation for "hashtable", and is used
  37. // as a parameter quite frequently.
  38. //
  39. // <object-table>s are as defined in the book, operating on pointers and
  40. // using == as a comparator.
  41. //
  42. // <equal-table>s use = as a key test, but since = uses == as a
  43. // default method, <equal-table>s also have to worry about garbage
  44. // collection.
  45. //
  46. // <value-table>s are an abstract class who's hash function never
  47. // involves addresses (ie, always returns $permanent-hash-state).  The
  48. // user defines a subclass of <value-table> and writes a method for
  49. // table-protocol.  This will probably involve writing a new hash
  50. // function to be used on the hash keys.  *Make sure this function does
  51. // not call object-hash*.
  52. //
  53. // For a more in depth explanation, see mindy.doc
  54.  
  55. // -------------------------------------------------------------------
  56. // Mindy-specific code
  57. // -------------------------------------------------------------------
  58.  
  59. // merge-hash-codes is predefined in Mindy. However, at present
  60. // merge-hash-states is not. This calls merge-hash-codes and throws
  61. // away information about the hash ids.
  62. //
  63. define method merge-hash-states (state1 :: <object>, state2 :: <object>) 
  64.           => merged :: <object>;
  65.   let (junk, new-state) = merge-hash-codes (0, state1, 0, state2);
  66.   new-state;
  67. end method merge-hash-states;
  68.  
  69. // -------------------------------------------------------------------
  70. // Stuff that Mindy takes care of, but other implementations might not:
  71. // -------------------------------------------------------------------
  72.  
  73. // Also be sure to verify that equal-hash and value-hash work as
  74. // advertised. They depend on object-hash (which is always defined,
  75. // but might not behave as Mindy's does) and float-hash (which is
  76. // implemented in Mindy but not standard).
  77.  
  78. // define constant $permanent-hash-state = #f;
  79. //
  80. // define constant magic-hash-constant = #x3fffffff;
  81. //         // And'ed with hash id's to keep the size under control 
  82. //         // when <integer> is <extended-integer>. This constant
  83. //         // should be as many consecutive 1 bits as will fit into a positive
  84. //         // <fixed-integer>.
  85. // 
  86. // define constant shift-dist          = 15;
  87. //         // This should be one half the size of an integer (in bits)
  88. //         // for reason that xor'ing the right shifted with the left
  89. //         // left shifted hash value is less sensical if shift-dist
  90. //         // is not 1/2 int size
  91. // 
  92. // define constant $permanent-hash-state = #f;
  93. // 
  94. // 
  95. // define method merge-hash-ids (id1 :: <integer>, id2 :: <integer>,
  96. //                   #key ordered: ordered = #f )
  97. //                     => hash-id :: <integer>;
  98. // 
  99. //   if (ordered)
  100. //     logand (magic-hash-constant,
  101. //         logxor (logxor (ash (id1, shift-dist),
  102. //                 ash (id1, -shift-dist)),
  103. //             id2));
  104. //   else
  105. //     logand (magic-hash-constant, logxor (id1, id2));
  106. //   end if;
  107. // end method merge-hash-ids;
  108. // 
  109. // 
  110. // define method merge-hash-states (state1, state2)
  111. //   if (state1 ~= $permanent-hash-state)
  112. //          if (state2 ~= $permanent-hash-state)
  113. //        min (state1, state2);
  114. //      else
  115. //        state1;
  116. //      end if;
  117. //   else
  118. //          state2;
  119. //   end if;
  120. // end method merge-hash-states;
  121. // 
  122. // 
  123. // define method merge-hash-codes (id1 :: <integer>, state1,
  124. //                 id2 :: <integer>, state2,
  125. //                 #key ordered: ordered = #f )
  126. // 
  127. //   values ( merge-hash-ids (id1, id2, ordered: ordered),
  128. //        merge-hash-states (state1, state2)
  129. //      );
  130. // end method merge-hash-codes;
  131.  
  132. // -------------------------------------------------------------------
  133. // Portable implementation
  134. // -------------------------------------------------------------------
  135.  
  136. // These numbers are expressed as percentages.  200 for expand-when means
  137. // when there are two objects for every bucket, the hash table will grow
  138. // to expand-to % of the original size.  (Make sure how-much is greater than
  139. // 100%, or you won't get what you want)
  140. //
  141. // Default-shrink-when and -to are handled similarly.  Shrink conditions
  142. // are checked only when someone removes an element, and expand only
  143. // when someone adds an element.  Be careful not to set shrink-when too
  144. // high, because if you do the table could shrink immediately after it
  145. // expands.
  146. //
  147. define constant default-starting-table-size :: <fixed-integer> =  5;
  148. define constant default-expand-when         :: <fixed-integer> = 200;
  149. define constant default-expand-to           :: <fixed-integer> = 300;
  150. define constant default-shrink-when         :: <fixed-integer> = 10;
  151. define constant default-shrink-to           :: <fixed-integer> = 100;
  152.  
  153.  
  154. define class <bucket-entry> (<object>)
  155.   slot key-slot                  , required-init-keyword: key:          ;
  156.   slot hash-id-slot  :: <fixed-integer>, required-init-keyword: hash-id:      ;
  157.   slot hash-state-slot           , required-init-keyword: hash-state:   ;
  158.   slot item-slot                 , required-init-keyword: item:         ;
  159. end class <bucket-entry>;
  160.  
  161.  
  162. define abstract class <table> (<dictionary>)
  163.   slot item-count-slot         :: <fixed-integer>;     // Number of keys
  164.   slot bucket-array-slot       :: <vector>;
  165.   slot bucket-count-slot       :: <fixed-integer>;     // size of bucket-array
  166.   slot bucket-states-slot      :: <vector>;
  167.   slot expand-when-slot        :: <fixed-integer>;
  168.   slot expand-to-slot          :: <fixed-integer>;
  169.   slot shrink-when-slot        :: <fixed-integer>;
  170.   slot shrink-to-slot          :: <fixed-integer>;
  171.   slot merged-hash-state-slot  :: <object>;
  172. end class <table>;
  173.       
  174.  
  175. // Uses == (aka id?) as key comparison
  176. //
  177. define class <object-table> (<table>)
  178. end class <object-table>;
  179.  
  180.  
  181. // Uses = as key comparison
  182. //
  183. define class <equal-table> (<table>)
  184. end class <equal-table>;
  185.  
  186.  
  187. // Uses a user defined key comparison and hash function, so long as
  188. // the hash function doesn't involve addresses.
  189. //
  190. define abstract class <value-table> (<table>)
  191. end class <value-table>;
  192.  
  193.  
  194. define method make-bucket-entry (key, hash-id :: <fixed-integer>, hash-state, item)
  195.  => entry :: <bucket-entry>;
  196.   make(<bucket-entry>,   
  197.        key:        key, 
  198.        hash-id:    hash-id, 
  199.        hash-state: hash-state,
  200.        item:       item);
  201. end method make-bucket-entry;
  202.  
  203.  
  204. define method make (c :: singleton (<table>), #rest key-value-pairs,
  205.             #all-keys)  =>  table :: <object-table>;
  206.   apply(make, <object-table>, key-value-pairs);
  207. end method make;
  208.  
  209.  
  210. define method initialize (ht :: <table>,
  211.               #next next-method,
  212.               #key size: size       = default-starting-table-size,
  213.               expand-when: expand-when = default-expand-when,
  214.               expand-to:   expand-to   = default-expand-to,
  215.               shrink-when: shrink-when = default-shrink-when,
  216.               shrink-to:   shrink-to   = default-shrink-to);
  217.  
  218.   ht.bucket-array-slot    := make(<simple-object-vector>, 
  219.                   size: size,
  220.                   fill: #() );     // filled with empty lists
  221.   ht.bucket-states-slot   := make(<simple-object-vector>,
  222.                   size: size,
  223.                   fill: $permanent-hash-state);
  224.   ht.item-count-slot        := 0;
  225.   ht.bucket-count-slot      := size;
  226.   ht.expand-when-slot       := expand-when;
  227.   ht.expand-to-slot         := expand-to;
  228.   ht.shrink-when-slot       := shrink-when;
  229.   ht.shrink-to-slot         := shrink-to;
  230.   ht.merged-hash-state-slot := $permanent-hash-state;
  231.   next-method();
  232. end method initialize;
  233.  
  234.  
  235. define method key-test (ht :: <table>) => test :: <function>;
  236.   let test = table-protocol(ht);    // drop the second return value
  237.   test;
  238. end method key-test;
  239.  
  240.  
  241. // equal-hash is used in the table-protocol as the hash-function 
  242. // for equal tables. Calling convention is similar to object-hash.
  243. //
  244. // The default method for objects that don't have any 
  245. // better methods defined. (We can't call object-hash, so what can we do?)
  246. //
  247. define method equal-hash (key :: <object>) 
  248.           => (id :: <fixed-integer>, state :: <object>);
  249.   values(42, $permanent-hash-state);
  250. end method equal-hash;
  251.  
  252.  
  253. // Call object-hash for characters, integers, symbols, classes,
  254. // functions, and conditions.
  255. //
  256. define method equal-hash (key :: <character>)
  257.           => (id :: <fixed-integer>, state :: <object>);
  258.   object-hash(key);
  259. end method equal-hash;
  260.  
  261.  
  262. define method equal-hash (key :: <integer>)
  263.           => (id :: <fixed-integer>, state :: <object>);
  264.   object-hash(key);
  265. end method equal-hash;
  266.  
  267.  
  268. define method equal-hash (key :: <float>)
  269.           => (id :: <fixed-integer>, state :: <object>);
  270.   float-hash(key);
  271. end method equal-hash;
  272.  
  273.  
  274. define method equal-hash (key :: <symbol>)
  275.           => (id :: <fixed-integer>, state :: <object>);
  276.   object-hash(key);
  277. end method equal-hash;
  278.  
  279.  
  280. define method equal-hash (key :: <class>)
  281.           => (id :: <fixed-integer>, state :: <object>);
  282.   object-hash(key);
  283. end method equal-hash;
  284.  
  285.  
  286. define method equal-hash (key :: <function>)
  287.           => (id :: <fixed-integer>, state :: <object>);
  288.   object-hash(key);
  289. end method equal-hash;
  290.  
  291.  
  292. define method equal-hash (key :: <type>)
  293.           => (id :: <fixed-integer>, state :: <object>);
  294.   object-hash(key);
  295. end method equal-hash;
  296.  
  297.  
  298. define method equal-hash (key :: singleton (#f))
  299.           => (id :: <fixed-integer>, state :: <object>);
  300.   object-hash(key);
  301. end method equal-hash;
  302.  
  303.  
  304. define method equal-hash (key :: singleton (#t))
  305.           => (id :: <fixed-integer>, state :: <object>);
  306.   object-hash(key);
  307. end method equal-hash;
  308.  
  309.  
  310. define method equal-hash (key :: <condition>)
  311.           => (id :: <fixed-integer>, state :: <object>);
  312.   object-hash(key);
  313. end method equal-hash;
  314.  
  315.  
  316. define method equal-hash (col :: <collection>)
  317.           => (id :: <fixed-integer>, state :: <object>);
  318.   collection-hash(col, equal-hash, equal-hash);
  319. end method equal-hash;
  320.  
  321.  
  322. // Object-hash returns $permanent-hash-state for <fix-num>s, the only
  323. // type of integer Mindy currently has. (Yes, ignore the "don't call
  324. // object-hash" warning at the beginning of this file. Trust me, this
  325. // works in *Mindy*) object-hash in Mindy does not return
  326. // $permanent-hash-state for anything else.
  327. //
  328. define method value-hash (key :: <integer>)
  329.           => (id :: <fixed-integer>, state :: <object>);
  330.   object-hash(key);
  331. end method value-hash;
  332.  
  333.  
  334. define method value-hash (key :: <float>)
  335.           => (id :: <fixed-integer>, state :: <object>);
  336.   float-hash(key);
  337. end method value-hash;
  338.  
  339.  
  340. define method value-hash (key :: <character>)
  341.           => (id :: <fixed-integer>, state :: <object>);
  342.   value-hash(as(<integer>, key));
  343. end method value-hash;
  344.  
  345.  
  346. define method value-hash (key :: <symbol>)
  347.           => (id :: <fixed-integer>, state :: <object>);
  348.   string-hash(as(<string>, key));
  349. end method value-hash;
  350.  
  351.  
  352. define method value-hash (key :: singleton (#f))
  353.           => (id :: <fixed-integer>, state :: <object>);
  354.   values(0, $permanent-hash-state);
  355. end method value-hash;
  356.  
  357.  
  358. define method value-hash (key :: singleton (#t))
  359.           => (id :: <fixed-integer>, state :: <object>);
  360.   values(1, $permanent-hash-state);
  361. end method value-hash;
  362.  
  363.  
  364. // You can't write a more specific method on collections because 
  365. // any two collections with identical key/element pairs are equal. 
  366. // Because of this, you can't merge-hash-codes with ordered: #t, or
  367. // really anything else interesting. In partial compensation, this
  368. // method hashes the keys as well as the elements. (As long as you
  369. // always put the element before the key when you merge hash codes,
  370. // you *can* use ordered: #t for merging them)
  371. //
  372. define method collection-hash(col :: <collection>, key-hash :: <function>,
  373.                   element-hash :: <function>)
  374.           => (id :: <fixed-integer>, state :: <object>);
  375.   let (current-id, current-state) = values(0, $permanent-hash-state);
  376.   for (elt keyed-by key in col)
  377.     let (elt-id, elt-state)           = element-hash(elt);
  378.     let (key-id, key-state)           = key-hash(key);
  379.     let (captured-id1, captured-state1) = merge-hash-codes(elt-id, elt-state,
  380.                                key-id, key-state,
  381.                                ordered: #t);
  382.     let (captured-id2, captured-state2) = merge-hash-codes(current-id, 
  383.                                current-state, 
  384.                                captured-id1,
  385.                                captured-state1,
  386.                                ordered: #f);
  387.     current-id    := captured-id2;
  388.     current-state := captured-state2;
  389.   end for;
  390.   values(current-id, current-state);
  391. end method collection-hash;
  392.  
  393.  
  394. // This is similar to an equal-hash, except that it hashes things with
  395. // ordered: #t and ignores the sequence keys. USE WITH CAUTION: This
  396. // isn't a proper equal-hash because two collections of different types
  397. // but identical key/element pairs won't generate the same hash id,
  398. // even though the two collections are =.
  399. //
  400. define method sequence-hash(seq :: <sequence>, element-hash :: <function>)
  401.           => (id :: <fixed-integer>, state :: <object>);
  402.   let (current-id, current-state) = values(0, $permanent-hash-state);
  403.   for (elt in seq)
  404.     let (id, state) = element-hash(elt);
  405.     let (captured-id, captured-state) = merge-hash-codes(current-id, 
  406.                              current-state, 
  407.                              id, state,
  408.                              ordered: #t);
  409.     current-id    := captured-id;
  410.     current-state := captured-state;
  411.   end for;
  412.   values(current-id, current-state);
  413. end method sequence-hash;
  414.  
  415.  
  416. // A convenient method for hashing strings. Calls sequence-hash 
  417. // and "does the right thing."
  418. //
  419. define method string-hash (s :: <string>)
  420.     => (id :: <fixed-integer>, state :: <object>);
  421.   sequence-hash(s, value-hash);
  422. end method string-hash;
  423.  
  424.  
  425. define method table-protocol(ht :: <object-table>) 
  426.          => (key-test :: <function>, key-hash :: <function>);
  427.   values(\==, object-hash);
  428. end method table-protocol;
  429.  
  430.  
  431. define method table-protocol(ht :: <equal-table>) 
  432.          => (key-test :: <function>, key-hash :: <function>);
  433.   values(\=, equal-hash);
  434. end method table-protocol;
  435.  
  436.  
  437. define constant not-in-ht2 = "not-in-ht2";
  438.  
  439. // Informally, two hash tables are = if they use the same key test,
  440. // have the same size, and all the elements in the first hash table
  441. // have matching elements in the second hash table.
  442. //
  443. define method \= (ht1 :: <table>, ht2 :: <table>);
  444.   let test1 = key-test (ht1);
  445.   let test2 = key-test (ht2);
  446.   (test1 == test2) 
  447.     & size(ht1) = size(ht2) 
  448.     & block (return)
  449.     for (elt1 keyed-by key in ht1)
  450.       let elt2 = element (ht2, key, default: not-in-ht2);
  451.       if (elt2 == not-in-ht2 | ~test1 (elt1, elt2))
  452.         return(#f);
  453.       end if;
  454.     end for;
  455.     #t;
  456.       end block;
  457. end method \=;
  458.  
  459.  
  460. // Returns the first element of the list that satisfies
  461. // test.  Internal use only.
  462. //
  463. define method find-elt (list :: <list>, test :: <function>,
  464.             #key default: default = #f )
  465.   if (empty?(list))
  466.     default;
  467.   else
  468.     if (test(head(list)))
  469.       head(list);
  470.     else
  471.       find-elt( tail (list), test, default: default);
  472.     end if;
  473.   end if;
  474. end method find-elt;
  475.  
  476.  
  477. define constant no-default = list("No default");
  478.  
  479. // This function looks redundant at times, but it's necessary in order
  480. // to avoid race conditions with the garbage collector.
  481. //
  482. define method element (  ht :: <table>, key, 
  483.                  #key default: default = no-default )
  484.   until (state-valid?(ht.merged-hash-state-slot))
  485.     rehash(ht);
  486.   end until;
  487.  
  488.   let (key=, key-hash)      = table-protocol(ht);
  489.   let (key-id, key-state)   = key-hash(key);
  490.   let bucket-index          = modulo(key-id, ht.bucket-count-slot);
  491.   let bucket                = ht.bucket-array-slot[bucket-index];
  492.   let test = method (entry :: <bucket-entry>)
  493.            (entry.hash-id-slot = key-id)
  494.          & key=(entry.key-slot, key);
  495.          end method;
  496.   let find-result = find-elt(bucket, test);
  497.   
  498.      // Check to see if there was a garbage collection in the middle
  499.      // of this method. If there was, start over.
  500.   
  501.   if (~ state-valid?(ht.merged-hash-state-slot)
  502.       | ~ state-valid?(key-state) )
  503.     element(ht, key, default: default);
  504.        
  505.     // Else, there was no garbage collection, and we're safe.
  506.   elseif (find-result)
  507.     find-result.item-slot;
  508.   elseif (default == no-default)
  509.     error("Element not found");
  510.   else 
  511.     default;
  512.   end if;
  513. end method element;
  514.  
  515.  
  516. // This is exactly the same code without the garbage collection stuff
  517. //
  518. define method element (  ht :: <value-table>, key, 
  519.                  #key default: default = no-default )
  520.   let (key=, key-hash)      = table-protocol(ht);
  521.   let key-id                = key-hash(key);
  522.   let bucket-index          = modulo(key-id, ht.bucket-count-slot);
  523.   let bucket                = ht.bucket-array-slot[bucket-index];
  524.   let test = method (entry :: <bucket-entry>)
  525.            (entry.hash-id-slot = key-id)
  526.          & key=(entry.key-slot, key);
  527.          end method;
  528.   let find-result = find-elt(bucket, test);
  529.   
  530.   if (find-result)
  531.     find-result.item-slot;
  532.   elseif (default == no-default)
  533.     error ("Element not found");
  534.   else 
  535.     default;
  536.   end if;
  537. end method element;
  538.  
  539.  
  540. // This function looks redundant at times, but it's necessary in order
  541. // to avoid race conditions with the garbage collector.
  542. //
  543. define method element-setter (value :: <object>, ht :: <table>, 
  544.                   key :: <object>) => value :: <object>;
  545.   until (state-valid? (ht.merged-hash-state-slot))
  546.     rehash (ht);
  547.   end until;
  548.  
  549.   let (key=, key-hash)    = table-protocol(ht);
  550.   let (key-id, key-state) = key-hash(key);
  551.   let bucket-index        = modulo(key-id, ht.bucket-count-slot);
  552.   let test-method         = method (existing-item :: <bucket-entry>)
  553.                   (existing-item.hash-id-slot = key-id)
  554.                 & key=(existing-item.key-slot, key);
  555.                 end method;
  556.   let bucket-entry        = find-elt(ht.bucket-array-slot [bucket-index],
  557.                      test-method);
  558.  
  559.      // Check to see if there was a garbage collection in the middle
  560.      // of this method. If there was, start over.
  561.  
  562.   if (~ state-valid?(ht.merged-hash-state-slot)
  563.       | ~ state-valid?(key-state) )
  564.     element-setter(value, ht, key);
  565.        
  566.              // Else, there was no garbage collection, and we're safe.
  567.              // (If there is a garbage collection between now and the
  568.              // the end of this method, it invalidates the states we're
  569.              // about to write, but we can just re-compute them on
  570.              // the next lookup)
  571.  
  572.   else
  573.  
  574.     if (bucket-entry = #f)             // If item didn't exist, add it
  575.       bucket-entry := make-bucket-entry(key, key-id, key-state, value);
  576.  
  577.       ht.bucket-array-slot[bucket-index] := 
  578.          pair(bucket-entry, ht.bucket-array-slot[bucket-index]);
  579.       ht.item-count-slot := ht.item-count-slot + 1;
  580.  
  581.       if (size(ht) * 100 > (ht.bucket-count-slot * ht.expand-when-slot))
  582.     resize-table(ht, truncate/(size(ht) * ht.expand-to-slot, 100) + 1);
  583.       end if;
  584.     else     // Item WAS found
  585.       bucket-entry.key-slot        := key;
  586.       bucket-entry.hash-id-slot    := key-id;
  587.       bucket-entry.hash-state-slot := key-state;
  588.       bucket-entry.item-slot       := value;
  589.     end if;
  590.  
  591.           // Update bucket's merged-hash-state
  592.     ht.bucket-states-slot[bucket-index] := 
  593.              merge-hash-states(bucket-entry.hash-state-slot, 
  594.                    ht.bucket-states-slot [bucket-index]);
  595.  
  596.     // Update table's merged hash codes
  597.     ht.merged-hash-state-slot := 
  598.       merge-hash-states(bucket-entry.hash-state-slot, 
  599.             ht.merged-hash-state-slot);
  600.     value;
  601.   end if;
  602. end method element-setter;
  603.  
  604.  
  605. // This is exactly the same code without the garbage collection stuff
  606. //
  607. define method element-setter (value :: <object>, ht :: <value-table>, 
  608.                   key :: <object>) => value :: <object>;
  609.   let (key=, key-hash)    = table-protocol(ht);
  610.   let key-id              = key-hash(key);
  611.   let bucket-index        = modulo(key-id, ht.bucket-count-slot);
  612.   let test-method         = method (existing-item :: <bucket-entry>)
  613.                   (existing-item.hash-id-slot = key-id)
  614.                 & key=(existing-item.key-slot, key);
  615.                 end method;
  616.   let bucket-entry        = find-elt(ht.bucket-array-slot [bucket-index],
  617.                      test-method);
  618.  
  619.   if (bucket-entry = #f)             // If item didn't exist, add it
  620.     bucket-entry := make-bucket-entry(key, key-id,
  621.                       $permanent-hash-state, 
  622.                       value);
  623.     
  624.     ht.bucket-array-slot[bucket-index] := 
  625.            pair(bucket-entry, ht.bucket-array-slot[bucket-index]);
  626.     ht.item-count-slot := ht.item-count-slot + 1;
  627.  
  628.     if (size(ht) * 100 > (ht.bucket-count-slot * ht.expand-when-slot))
  629.       resize-table(ht, truncate/ (size(ht) * ht.expand-to-slot, 100) + 1);
  630.     end if;
  631.   else     // Item WAS found
  632.     bucket-entry.key-slot        := key;
  633.     bucket-entry.hash-id-slot    := key-id;
  634.     bucket-entry.item-slot       := value;
  635.   end if;
  636.   value;
  637. end method element-setter;
  638.  
  639.  
  640. define method remove-key! (ht :: <table>, key) => new-ht :: <table>;
  641.   until (state-valid?(ht.merged-hash-state-slot))
  642.     rehash(ht);
  643.   end until;
  644.  
  645.   let (key=, key-hash)      = table-protocol(ht);
  646.   let (key-id, key-state)   = key-hash(key);
  647.   let bucket-index          = modulo (key-id, ht.bucket-count-slot);
  648.   let bucket                = ht.bucket-array-slot[bucket-index];
  649.   let test = method (existing-item :: <bucket-entry>)
  650.            (existing-item.hash-id-slot = key-id)
  651.          & key= (existing-item.key-slot, key);
  652.          end method;
  653.   let the-item = find-elt(bucket, test);
  654.  
  655.   if (~state-valid?(ht.merged-hash-state-slot)
  656.       | ~state-valid?(key-state))
  657.     remove-key!(ht, key);    // If state not valid, goto beginning
  658.                  // for a rehash
  659.   else
  660.     if (the-item ~= #f)       // An item with that key was found
  661.     ht.item-count-slot := ht.item-count-slot - 1;
  662.  
  663.            // Between find-elt and remove!, this traverses the bucket
  664.            // twice. It could be improved, but one has to be careful 
  665.            // to avoid race conditions with the garbage collector.
  666.  
  667.     ht.bucket-array-slot[bucket-index] := remove!(bucket, the-item);
  668.  
  669.         if (size (ht) * 100 < (ht.bucket-count-slot * ht.shrink-when-slot))
  670.       resize-table(ht, truncate/ (size(ht) * ht.shrink-to-slot, 100) + 1);
  671.     end if;
  672.  
  673.       // We leave all the merged-states as is. rehash will take care of it
  674.       // if a remove-key! made the merged-state information overly cautious.
  675.  
  676.     end if; // had to remove something
  677.     ht;
  678.   end if;   // states valid?
  679. end method remove-key!;
  680.  
  681.  
  682. // This is exactly the same code without the garbage collection stuff
  683. //
  684. define method remove-key! (ht :: <value-table>, key) => new-ht :: <table>;
  685.   let (key=, key-hash)      = table-protocol(ht);
  686.   let key-id                = key-hash(key);
  687.   let bucket-index          = modulo(key-id, ht.bucket-count-slot);
  688.   let bucket                = ht.bucket-array-slot[bucket-index];
  689.  
  690.   let test = method (existing-item :: <bucket-entry>)
  691.            (existing-item.hash-id-slot = key-id)
  692.          & key=(existing-item.key-slot, key);
  693.          end method;
  694.   let the-item = find-elt(bucket, test);
  695.  
  696.   if (the-item ~= #f)       // An item with that key was found
  697.     ht.item-count-slot := ht.item-count-slot - 1;
  698.  
  699.            // Between find-elt and remove!, this traverses the bucket
  700.            // twice. It could be improved.
  701.  
  702.     ht.bucket-array-slot[bucket-index] := remove!(bucket, the-item);
  703.  
  704.     if (size(ht) * 100 < (ht.bucket-count-slot * ht.shrink-when-slot))
  705.       resize-table(ht, truncate/(size(ht) * ht.shrink-to-slot, 100) + 1);
  706.     end if;
  707.   end if; // had to remove something
  708.   ht;
  709. end method remove-key!;
  710.  
  711.  
  712. // Takes a hashtable and mutates it so that it has a different number of
  713. // buckets.
  714. //
  715. define method resize-table (ht :: <table>, numbuckets :: <fixed-integer>);
  716.   let new-array = make(<simple-object-vector>, 
  717.                size: numbuckets,
  718.                fill: #()   );
  719.   let new-state-array = make(<simple-object-vector>,
  720.                  size: numbuckets,
  721.                  fill: $permanent-hash-state   );
  722.  
  723.   for (bucket in ht.bucket-array-slot)
  724.     for (entry in bucket)
  725.       let index = modulo(entry.hash-id-slot, numbuckets);
  726.       new-array[index] := pair(entry, new-array [index]);
  727.       new-state-array[index] := merge-hash-states(new-state-array [index],
  728.                            entry.hash-state-slot);
  729.     end for;
  730.   end for;
  731.  
  732.   ht.bucket-array-slot  := new-array;
  733.   ht.bucket-states-slot := new-state-array;
  734.   ht.bucket-count-slot  := numbuckets;
  735. end method resize-table;
  736.  
  737.  
  738. // This version of resize-table doesn't bother updating any of the
  739. // merged state slots, arrays, etc.
  740. //
  741. define method resize-table (ht :: <value-table>, numbuckets :: <fixed-integer>)
  742.   let new-array = make(<simple-object-vector>, 
  743.                size: numbuckets,
  744.                fill: #()   );
  745.  
  746.   for (bucket in ht.bucket-array-slot)
  747.     for (entry in bucket)
  748.       let index = modulo(entry.hash-id-slot, numbuckets);
  749.       new-array[index] := pair(entry, new-array[index]);
  750.     end for;
  751.   end for;
  752.  
  753.   ht.bucket-array-slot := new-array;
  754.   ht.bucket-count-slot := numbuckets;
  755. end method resize-table;
  756.  
  757.  
  758. // Rehash does its best to bring a table up to date so that all the
  759. // hash-id's in the table are valid. Rehash makes no guarentees about
  760. // its success, however, so one should call it inside an until loop
  761. // to make sure it keeps trying until it succeeds.
  762. //
  763. // Rehash wants to get the merged-hash-states to be as accurate as
  764. // possible without sacraficing too much performance. This might be a
  765. // good function to tune.
  766. //
  767. define method rehash (ht :: <table>) => rehashed-ht :: <table>;
  768.   let (key=, key-hash)  =  table-protocol(ht);
  769.  
  770.   for (i from 0 below ht.bucket-count-slot)
  771.     if (~ state-valid?(ht.bucket-states-slot[i]))     // rehash bucket
  772.       ht.bucket-states-slot[i] := $permanent-hash-state;
  773.  
  774.       let bucket    = ht.bucket-array-slot[i];
  775.       let prev      = #f;
  776.       let remaining = bucket;
  777.       
  778.              // This until is just like remove!, except that it
  779.          // rehashes things
  780.       until (remaining == #())
  781.     let bucket-entry = head(remaining);
  782.     let index        = i;
  783.  
  784.     if (state-valid?(bucket-entry.hash-state-slot))
  785.       prev        := remaining;
  786.       remaining   := tail(remaining);
  787.  
  788.     else  // state is invalid
  789.       let (id, state) = key-hash(bucket-entry.key-slot);  
  790.       bucket-entry.hash-id-slot    := id;
  791.       bucket-entry.hash-state-slot := state;
  792.       index := modulo(id, ht.bucket-count-slot);
  793.       if (index = i)          // Keep its place in the list
  794.         prev := remaining;
  795.         remaining := tail(remaining);
  796.       else                    // Move entry
  797.         ht.bucket-array-slot [index] := 
  798.              pair(bucket-entry, ht.bucket-array-slot [index]);
  799.  
  800.               // Now remove it from old bucket
  801.         if (prev)
  802.           tail(prev) := tail(remaining);
  803.           remaining   := tail(remaining);
  804.         else
  805.           bucket      := tail(remaining);
  806.           prev        := #f;
  807.           remaining   := tail(remaining);
  808.         end if;  // If prev
  809.       end if;    // If index = i
  810.     end if;      // If state-valid? (bucket-entry)
  811.  
  812.     ht.bucket-array-slot[i] := bucket;
  813.     ht.bucket-states-slot[index] := 
  814.               merge-hash-states(bucket-entry.hash-state-slot,
  815.                      ht.bucket-states-slot[index]);
  816.       end until;    // Finished traversing the bucket
  817.     end if;         // state-valid? (bucket-id-slots)
  818.   end for;
  819.   ht.merged-hash-state-slot := reduce(merge-hash-states,
  820.                       $permanent-hash-state,
  821.                       ht.bucket-states-slot);
  822.   ht;
  823. end method rehash;
  824.  
  825.  
  826. define method size (ht :: <table>)
  827.   ht.item-count-slot;
  828. end method size;
  829.  
  830.  
  831. define method empty? (ht :: <table>)
  832.   ht.item-count-slot = 0;
  833. end method empty?;
  834.  
  835.  
  836. // Inherit mapping functions
  837.  
  838. // -------------------------------------------------------------------
  839. //                Iteration protocol stuff
  840. // -------------------------------------------------------------------
  841.  
  842. // All these things are needed in the state, because many of the functions
  843. // get nothing but a hash table and a state.
  844.  
  845.  
  846. // This is the iteration state, not a hash-state
  847. //
  848. define class <ntable-state> (<object>)
  849.   slot elements-touched-slot,         init-keyword: elements-touched:      ;
  850.  
  851.   slot array-state-slot,              init-keyword: array-state:           ;
  852.   slot array-limit-slot,              init-keyword: array-limit:           ;
  853.   slot array-next-state-slot,         init-keyword: array-next-state:      ;
  854.   slot array-finished-state?-slot,    init-keyword: array-finished-state?: ;
  855.   slot array-current-key-slot,        init-keyword: array-current-key:     ;
  856.   slot array-current-element-slot,    init-keyword: array-current-element: ;
  857.   slot array-current-element-setter-slot,   
  858.                           init-keyword: array-current-element-setter:      ;
  859.   slot array-copy-state-slot,         init-keyword: array-copy-state:      ;
  860.  
  861.   slot bucket-state-slot,             init-keyword: bucket-state:          ;
  862.   slot bucket-limit-slot,             init-keyword: bucket-limit:          ;
  863.   slot bucket-next-state-slot,        init-keyword: bucket-next-state:     ;
  864.   slot bucket-finished-state?-slot,   init-keyword: bucket-finished-state?:;
  865.   slot bucket-current-key-slot,       init-keyword: bucket-current-key:    ;
  866.   slot bucket-current-element-slot,   init-keyword: bucket-current-element:;
  867.   slot bucket-current-element-setter-slot,       
  868.                            init-keyword: bucket-current-element-setter:    ;
  869.   slot bucket-copy-state-slot,        init-keyword: bucket-copy-state:     ;
  870. end class <ntable-state>;
  871.  
  872.  
  873. define method finished-table-state? (ht :: <table>,
  874.                      state :: <ntable-state>,
  875.                      limit)
  876.   state.elements-touched-slot >= ht.item-count-slot;
  877. end method finished-table-state?;
  878.  
  879.  
  880. define method next-table-state (ht    :: <table>,
  881.                    state :: <ntable-state>) 
  882.                => new-state :: <ntable-state>;
  883.   state.elements-touched-slot := state.elements-touched-slot + 1;
  884.   if (~finished-table-state?(ht, state, #f))
  885.     let bucket = state.array-current-element-slot(ht.bucket-array-slot,
  886.                           state.array-state-slot);
  887.     state.bucket-state-slot := 
  888.                 state.bucket-next-state-slot(bucket, state.bucket-state-slot);
  889.     if (state.bucket-finished-state?-slot(bucket,
  890.                       state.bucket-state-slot,
  891.                       state.bucket-limit-slot))
  892.       // Then move on to the next bucket
  893.       state.array-state-slot := 
  894.     state.array-next-state-slot(ht.bucket-array-slot,
  895.                     state.array-state-slot);
  896.  
  897.       bucket := state.array-current-element-slot(ht.bucket-array-slot,
  898.                          state.array-state-slot);
  899.       while (empty?(bucket))
  900.     state.array-state-slot := 
  901.                  state.array-next-state-slot(ht.bucket-array-slot,
  902.                          state.array-state-slot);
  903.     bucket := state.array-current-element-slot(ht.bucket-array-slot,
  904.                            state.array-state-slot);
  905.       end while;
  906.       let (next-bucket-initial-state,
  907.        next-bucket-limit,
  908.        next-bucket-next-state,
  909.        next-bucket-finished-state?,
  910.        next-bucket-current-key,
  911.        next-bucket-current-element,
  912.        next-bucket-current-element-setter,
  913.        next-bucket-copy-state) = 
  914.                       forward-iteration-protocol(bucket);
  915.       state.bucket-state-slot                  := next-bucket-initial-state;
  916.       state.bucket-limit-slot                  := next-bucket-limit;
  917.       state.bucket-next-state-slot             := next-bucket-next-state;
  918.       state.bucket-finished-state?-slot        := next-bucket-finished-state?;
  919.       state.bucket-current-key-slot            := next-bucket-current-key;
  920.       state.bucket-current-element-slot        := next-bucket-current-element;
  921.       state.bucket-current-element-setter-slot :=
  922.                             next-bucket-current-element-setter;
  923.       state.bucket-copy-state-slot             := next-bucket-copy-state;    
  924.     end if;           // End of things to do if bucket ran dry
  925.   end if;             // End of more objects left in hash table?
  926.   state;            // Return the new and improved state object
  927. end method next-table-state;
  928.  
  929.  
  930. define method get-bucket-entry (ht :: <table>, state :: <ntable-state>)
  931.                   => entry :: <bucket-entry>;
  932.   let bucket = state.array-current-element-slot(ht.bucket-array-slot,
  933.                         state.array-state-slot);
  934.   state.bucket-current-element-slot(bucket, state.bucket-state-slot);
  935. end method get-bucket-entry;
  936.  
  937.  
  938. define method current-table-key (ht :: <table>, state :: <ntable-state>)
  939.   let bucket-entry = get-bucket-entry(ht, state);
  940.   bucket-entry.key-slot;
  941. end method current-table-key;
  942.  
  943.  
  944. define method current-table-element (ht :: <table>, state :: <ntable-state>)
  945.   let bucket-entry = get-bucket-entry(ht, state);
  946.   bucket-entry.item-slot;
  947. end method current-table-element;
  948.  
  949.  
  950. define method current-table-element-setter (value,
  951.                        ht    :: <table>,
  952.                        state :: <ntable-state>)
  953.          // This argument order isn't mentioned anywhere I can find,
  954.          // but seems to be what is expected
  955.  
  956.   let bucket = state.array-current-element-slot(ht.bucket-array-slot,
  957.                         state.array-state-slot);
  958.   let new-bucket-entry = get-bucket-entry(ht, state);
  959.   new-bucket-entry.item-slot := value;
  960.   state.bucket-current-element-setter-slot(new-bucket-entry,
  961.                        bucket,
  962.                        state.bucket-state-slot);
  963.   value;
  964. end method current-table-element-setter;
  965.  
  966.  
  967. define method copy-table-state (ht :: <table>, old-state :: <ntable-state>)
  968.   let bucket    = old-state.array-current-element-slot(ht.bucket-array-slot,
  969.                           old-state.array-state-slot);
  970.   let new-state = make(<ntable-state>);
  971.   new-state.array-state-slot  :=
  972.     old-state.array-copy-state-slot(ht.bucket-array-slot,
  973.                     old-state.array-state-slot);
  974.   new-state.bucket-state-slot := 
  975.     old-state.bucket-copy-state-slot(bucket, old-state.bucket-state-slot);
  976.  
  977.   new-state.array-next-state-slot      := old-state.array-next-state-slot;
  978.   new-state.array-copy-state-slot      := old-state.array-copy-state-slot;
  979.   new-state.array-current-key-slot     := old-state.array-current-key-slot;
  980.   new-state.array-finished-state?-slot :=
  981.              old-state.array-finished-state?-slot;
  982.   new-state.array-current-element-slot := 
  983.              old-state.array-current-element-slot;
  984.   new-state.array-current-element-setter-slot :=
  985.              old-state.array-current-element-setter-slot;
  986.  
  987.   new-state.bucket-next-state-slot      := old-state.bucket-next-state-slot;
  988.   new-state.bucket-copy-state-slot      := old-state.bucket-copy-state-slot;
  989.   new-state.bucket-current-key-slot     := old-state.bucket-current-key-slot;
  990.   new-state.bucket-finished-state?-slot :=
  991.              old-state.bucket-finished-state?-slot;
  992.   new-state.bucket-current-element-slot := 
  993.              old-state.bucket-current-element-slot;
  994.   new-state.bucket-current-element-setter-slot :=
  995.              old-state.bucket-current-element-setter-slot;
  996.  
  997.   new-state;
  998. end method copy-table-state;
  999.  
  1000.  
  1001. define method make-table-state (ht :: <table>) 
  1002.                => table-state :: <ntable-state>;
  1003.   let (array-initial-state,
  1004.        array-limit,
  1005.        array-next-state,
  1006.        array-finished-state?,
  1007.        array-current-key,
  1008.        array-current-element,
  1009.        array-current-element-setter,
  1010.        array-copy-state) = forward-iteration-protocol(ht.bucket-array-slot);
  1011.   let init-state = make(<ntable-state>);
  1012.  
  1013.   init-state.elements-touched-slot :=             0;
  1014.  
  1015.   init-state.array-state-slot :=                  array-initial-state;
  1016.   init-state.array-limit-slot :=                  array-limit;
  1017.   init-state.array-next-state-slot :=             array-next-state;
  1018.   init-state.array-finished-state?-slot :=        array-finished-state?;
  1019.   init-state.array-current-key-slot :=            array-current-key;
  1020.   init-state.array-current-element-slot :=        array-current-element;
  1021.   init-state.array-current-element-setter-slot := array-current-element-setter;
  1022.   init-state.array-copy-state-slot :=             array-copy-state;
  1023.  
  1024.   if (ht.item-count-slot > 0)
  1025.     let bucket = init-state.array-current-element-slot (ht.bucket-array-slot,
  1026.                                   init-state.array-state-slot);
  1027.  
  1028.     while (empty?(bucket))             // Find first non-empty bucket
  1029.       init-state.array-state-slot := 
  1030.     init-state.array-next-state-slot (ht.bucket-array-slot,
  1031.                       init-state.array-state-slot);
  1032.       bucket := init-state.array-current-element-slot (ht.bucket-array-slot,
  1033.                         init-state.array-state-slot);
  1034.     end while;
  1035.  
  1036.           // In the case that the hash table is empty, the bucket states
  1037.           // are neither initialized nor needed.
  1038.  
  1039.     let (first-bucket-initial-state,
  1040.      first-bucket-limit,
  1041.      first-bucket-next-state,
  1042.      first-bucket-finished-state?,
  1043.      first-bucket-current-key,
  1044.      first-bucket-current-element,
  1045.      first-bucket-current-element-setter,
  1046.      first-bucket-copy-state) = 
  1047.                     forward-iteration-protocol(bucket);
  1048.  
  1049.     init-state.bucket-state-slot :=              first-bucket-initial-state;
  1050.     init-state.bucket-limit-slot :=              first-bucket-limit;
  1051.     init-state.bucket-next-state-slot :=         first-bucket-next-state;
  1052.     init-state.bucket-finished-state?-slot :=    first-bucket-finished-state?;
  1053.     init-state.bucket-current-key-slot :=        first-bucket-current-key;
  1054.     init-state.bucket-current-element-slot :=    first-bucket-current-element;
  1055.     init-state.bucket-current-element-setter-slot := 
  1056.                                          first-bucket-current-element-setter;
  1057.     init-state.bucket-copy-state-slot :=         first-bucket-copy-state;
  1058.   end if;
  1059.   init-state;                        // Return value
  1060. end method make-table-state;
  1061.  
  1062.  
  1063. define method forward-iteration-protocol (ht :: <table>)
  1064.   values (make-table-state(ht),       // initial hash state
  1065.       #f,             // limit -- isn't actually used by finished-state?
  1066.       next-table-state,
  1067.       finished-table-state?,
  1068.       current-table-key,
  1069.       current-table-element,
  1070.       current-table-element-setter,
  1071.       copy-table-state);
  1072. end method forward-iteration-protocol;
  1073.